// -----------------------------
// Author: 		Dhaenens Gert	
// Creation: 	12/11/2014 10:56:13 AM				
// -----------------------------

#include "SkyboxGenerator.h"

#include "Application.h"
#include "RenderManager.h"

#include "Noise1234.h"

ITexture2D* SkyboxGenerator::Generate(Application* lpApp, const SkyboxDesc& desc)
{
	SizeT faceDim = desc.Width * desc.Height;
	SizeT faceSize = faceDim * 4;
	SizeT faces = 6;

	Float* lpData = new Float[faceSize * faces];
	memset(lpData, 0, faceSize * faces * sizeof(Float));

	// Generate the Side Faces. Face 0, 1, 4, & 5
	GenerateSideFace(lpApp, desc, lpData + (faceSize * 1));
	GenerateSideFace(lpApp, desc, lpData + (faceSize * 4));
	GenerateSideFace(lpApp, desc, lpData + (faceSize * 0));
	GenerateSideFace(lpApp, desc, lpData + (faceSize * 5));

	// Fill the Bottom & Top Face
	FillFace(lpApp, desc, lpData + (faceSize * 2), desc.SkyColor); // Top
	FillFace(lpApp, desc, lpData + (faceSize * 3), desc.HorizonColor); // Bottom

	// Generate Side Clouds
	GenerateSideCloud(lpApp, desc, lpData + (faceSize * 1), 0, desc.Width, true ); // Left
	GenerateSideCloud(lpApp, desc, lpData + (faceSize * 4), desc.Width, desc.Width, false ); // Back
	GenerateSideCloud(lpApp, desc, lpData + (faceSize * 0), desc.Width, desc.Width, true ); // Right
	GenerateSideCloud(lpApp, desc, lpData + (faceSize * 5), 0, desc.Width, false ); // Front

	// Generate Top Cloud
	GenerateFaceCloud(lpApp, desc, lpData + (faceSize * 2));

	ITexture2D* lpTexture = ITexture2D::CreateCubemap(lpApp->GetRenderer()->GetDevice(), desc.Width, desc.Height, Format::Float4, lpData, faceSize * sizeof(Float));

	delete[] lpData;

	return lpTexture;
}

void SkyboxGenerator::GenerateSideFace(Application* lpApp, const SkyboxDesc& desc, Float* lpAddress)
{
	UInt32 minY = (UInt32) (desc.Height * (1.0f - desc.HorizonEnd));
	UInt32 maxY = (UInt32) (desc.Height * (1.0f - desc.HorizonStart));

	for (UInt32 y = 0; y < desc.Height; ++y)
	{
		Float lerpVal = ((Float) y - minY) / (maxY - minY);
		lerpVal = Math::Clamp(lerpVal, 0.0f, 1.0f);
		lerpVal = Math::Pow(lerpVal, desc.HorizonPower);

		Color clr = Color::Lerp(desc.HorizonColor, desc.SkyColor, 1.0f - lerpVal);

		for (UInt32 x = 0; x < desc.Width; ++x)
		{
			UInt32 idx = (y * desc.Width) + x;
			idx *= 4;

			lpAddress[idx + 0] = clr.r;
			lpAddress[idx + 1] = clr.g;
			lpAddress[idx + 2] = clr.b;
			lpAddress[idx + 3] = clr.a;
		}
	}
}
void SkyboxGenerator::FillFace(Application* lpApp, const SkyboxDesc& desc, Float* lpAddress, const Color& clr)
{
	for (UInt32 y = 0; y < desc.Height; ++y)
	{
		for (UInt32 x = 0; x < desc.Width; ++x)
		{
			UInt32 idx = (y * desc.Width) + x;
			idx *= 4;

			lpAddress[idx + 0] = clr.r;
			lpAddress[idx + 1] = clr.g;
			lpAddress[idx + 2] = clr.b;
			lpAddress[idx + 3] = clr.a;
		}
	}
}
void SkyboxGenerator::FillFaceChecker(Application* lpApp, const SkyboxDesc& desc, Float* lpAddress)
{
	UInt32 checkerSize = 50;
	for (UInt32 y = 0; y < desc.Height; ++y)
	{
		for (UInt32 x = 0; x < desc.Width; ++x)
		{
			UInt32 idx = (y * desc.Width) + x;
			idx *= 4;

			Color nclr = Color::White;
			UInt32 a = (((y & 0x8) == 0) ^ ((x & 0x8) == 0));
			if (a == 0)
				nclr = Color::Black;


			lpAddress[idx + 0] = nclr.r;
			lpAddress[idx + 1] = nclr.g;
			lpAddress[idx + 2] = nclr.b;
			lpAddress[idx + 3] = nclr.a;
		}
	}
}
void SkyboxGenerator::GenerateSideCloud(Application* lpApp, const SkyboxDesc& desc, Float* lpAddress, UInt32 otherValue, UInt32 otherMaxValue, Bool isSideFace)
{
	Float* lpData = new Float[desc.Width * desc.Height];
	memset(lpData, 0, desc.Width * desc.Height * sizeof(Float));

	UInt32 maxY = (UInt32)(desc.Height * (1.0f - desc.CloudStart));
	UInt32 minY = (UInt32)(desc.Height * (1.0f - desc.CloudEnd));

	UInt32 borderX = (UInt32)(desc.Width * desc.CloudBorder);
	UInt32 borderY = (UInt32)(desc.Height * desc.CloudBorder);

	for (UInt32 y = minY; y < maxY; ++y)
	{
		Float alphaTopY = 1.0f;
		Float alphaBottomY = 1.0f;
		if (y < (minY + borderY))
			alphaTopY = (Float)(y - minY) / borderY;
		if (y > maxY - borderY)
			alphaBottomY = 1.0f - ((Float)(y - (maxY - borderY)) / borderY);

		for (UInt32 x = 0; x < desc.Width; ++x)
		{
			Float alphaX = 1.0f;
			if (x < borderX)
				alphaX = (Float)x / borderX;
			if (x > desc.Width - borderX)
				alphaX = 1.0f - ((Float)(x - (desc.Width - borderX)) / borderX);

			Float px = (Float) x / (desc.Width);
			Float py = (Float) y / (desc.Height);
			Float pz = (Float)otherValue / (otherMaxValue);
			if (isSideFace)
			{
				// If it is a side face, swap z & x
				Float opx = px;
				px = pz;
				pz = opx;
			}

			Float a = Noise1234::scaled_octave_noise(16, 0.05f, desc.CloudScale, px, py, pz);
			a = (a + 1.0f) / 2.0f;
			a = Math::Max(0.0f, a - desc.CloudThreshold);
			a /= 1.0f - desc.CloudThreshold;
			a *= alphaX;
			a *= alphaTopY;
			a *= alphaBottomY;

			Color clr = desc.CloudColor;

			UInt32 idx = (y * desc.Width) + x;
			lpData[idx] = a;
		}
	}

	for (UInt32 y = 0; y < desc.Height; ++y)
	{
		for (UInt32 x = 0; x < desc.Width; ++x)
		{
			Color clr = desc.CloudColor;
	
			UInt32 idx = (y * desc.Width) + x;
	
			lpAddress[(idx * 4) + 0] = Math::Lerp(lpAddress[(idx * 4) + 0], clr.r, lpData[idx]);
			lpAddress[(idx * 4) + 1] = Math::Lerp(lpAddress[(idx * 4) + 1], clr.g, lpData[idx]);
			lpAddress[(idx * 4) + 2] = Math::Lerp(lpAddress[(idx * 4) + 2], clr.b, lpData[idx]);
			lpAddress[(idx * 4) + 3] = clr.a;
		}
	}

	delete[] lpData;
}
void SkyboxGenerator::GenerateFaceCloud(Application* lpApp, const SkyboxDesc& desc, Float* lpAddress)
{
	UInt32 borderX = (UInt32)(desc.Width * desc.CloudBorder);
	UInt32 borderY = (UInt32)(desc.Height * desc.CloudBorder);

	for (UInt32 y = 0; y < desc.Height; ++y)
	{
		Float alphaY = 1.0f;
		if (y < borderY)
			alphaY = (Float) y / borderY;
		if (y > desc.Height - borderY)
			alphaY = 1.0f - ((Float) (y - (desc.Height - borderY)) / borderY);

		for (UInt32 x = 0; x < desc.Width; ++x)
		{
			Float alphaX = 1.0f;
			if (x < borderX)
				alphaX = (Float) x / borderX;
			if (x > desc.Width - borderX)
				alphaX = 1.0f - ((Float) (x - (desc.Width - borderX)) / borderX);

			Float px = (Float)x / (desc.Width);
			Float py = (Float)desc.Height / (desc.Height);
			Float pz = (Float)y / (desc.Height);

			Float a = Noise1234::scaled_octave_noise(16, 0.05f, desc.CloudScale, px, py, pz);
			a = (a + 1.0f) / 2.0f;

			a = Math::Max(0.0f, a - desc.CloudThreshold);
			a /= 1.0f - desc.CloudThreshold;
			a *= alphaY;
			a *= alphaX;

			UInt32 idx = (y * desc.Width) + x;
			Color* lpColorAddress = reinterpret_cast<Color*>(lpAddress);
			Color originalColor = lpColorAddress[idx];
			Color destColor = Color::Lerp(originalColor, desc.CloudColor, a);
			lpColorAddress[idx] = destColor;
		}
	}

}